<?php

/**
 * @Author: 李红雨 - RainLee <rainlee1990@yeah.net>
 * @Date: 2022-03-29 20:48:30
 * @LastEditors: 李红雨 - RainLee <rainlee1990@yeah.net>
 * @LastEditTime: 2022-03-29 21:37:16
 * @Description: 文件说明
 */

namespace rainlee\auth\jwt;

use InvalidArgumentException;
use DateTimeImmutable;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\Plain;
use rainlee\auth\Helper;
use think\facade\Request;

class JwtHandler
{

    /**
     * 守卫器
     */
    protected $guard = '';

    /**
     * 配置
     */
    protected $config;

    /**
     * 解析后的Token
     */
    protected $token = null;

    public function __construct($name, $config)
    {
        $this->guard  = $name;
        $this->config = $config;
    }

    /**
     * 配置加密秘钥
     * 
     * @return Configuration
     */
    protected function configure()
    {
        return Configuration::forSymmetricSigner(
            $this->config->getSigner(),
            InMemory::base64Encoded($this->config->getSignerKey())
        );
    }

    /**
     * 生成Token
     * @param array $claims
     * @return Token
     */
    public function build($claims = [])
    {
        $configure = $this->configure();
        assert($configure instanceof Configuration);

        $now = new DateTimeImmutable();
        $build = $configure->builder(ChainedFormatter::withUnixTimestampDates())
            // 签发人
            ->issuedBy($this->config->getIss())
            // 受众
            ->permittedFor($this->config->getAud())
            // 签发时间
            ->issuedAt($now)
            // Token生效时间
            ->canOnlyBeUsedAfter($now->modify('+' . $this->config->getEffectiveAt() . ' second'))
            // 过期时间
            ->expiresAt($now->modify('+' . $this->config->getExpiresAt() . ' second'));

        if (!$claims[$this->config->getUniqueIndex()]) {
            throw new InvalidArgumentException("Unique index value not set.");
        }

        if ($this->config->getVerifyIP()) {
            $build = $build->withClaim('ipv4', Helper::getClientIp());
        }

        foreach ($claims as $key => $value) {
            $build = $build->withClaim($key, $value);
        }

        // 生成token
        return $build->getToken($configure->signer(), $configure->signingKey())->toString();
    }

    /**
     * 验证Token
     * 
     * @param string|null $token;
     * @return bool
     */
    public function validation($token = null)
    {
        $token = $token ?: $this->getToken();

        if (is_null($token)) return false;

        $token = $this->parse($token);

        $configure = $this->configure();
        assert($configure instanceof Configuration);

        //验证签发人url是否正确
        $validate_issued = new \Lcobucci\JWT\Validation\Constraint\IssuedBy($this->config->getIss());
        $configure->setValidationConstraints($validate_issued);
        //验证客户端url是否匹配
        $validate_aud = new \Lcobucci\JWT\Validation\Constraint\PermittedFor($this->config->getAud());
        $configure->setValidationConstraints($validate_aud);

        //验证是否过期
        $timezone = new \DateTimeZone('Asia/Shanghai');
        $now = new \Lcobucci\Clock\SystemClock($timezone);
        $validate_jwt_at = new \Lcobucci\JWT\Validation\Constraint\StrictValidAt($now);
        $configure->setValidationConstraints($validate_jwt_at);

        $constraints = $configure->validationConstraints();

        // 验证Token
        if (!$configure->validator()->validate($token, ...$constraints)) {
            return false;
        }

        // 验证客户端IP
        if ($this->config->getVerifyIP() && $token->claims()->get('ipv4') !== Helper::getClientIp()) {
            return false;
        }

        return true;
    }

    /**
     * 解析Token
     * 
     * @param string|null $token
     * @return \Lcobucci\JWT\Token\Plain
     */
    protected function parse($token = null)
    {
        if (!is_null($this->token)) return $this->token;

        $configure = $this->configure();
        assert($configure instanceof Configuration);

        $token = $configure->parser()->parse($token);

        assert($token instanceof Plain);

        $this->token = $token;

        return $this->token;
    }

    /**
     * 获取Token内所有荷载数据
     * 
     * @return array
     */
    public function getClaims()
    {
        $token = $this->parse();

        return $token->claims()->all();
    }

    /**
     * 获取唯一索引数据
     * 
     * @return mixed
     */
    public function getId()
    {
        $token = $this->parse();

        return $token->claims()->get($this->config->getUniqueIndex());
    }

    /**
     * 获取Token
     */
    protected function getToken()
    {
        $token_mode = $this->config->getTokenModel();
        foreach ($token_mode as $handle) {
            switch (strtolower($handle)) {
                case 'header':
                    $authorization = Request::header('authorization');

                    if (!$authorization) {
                        return null;
                    }

                    if (strpos($authorization, 'Bearer ') === 0) {
                        return substr($authorization, 7);
                    } else {
                        return $authorization;
                    }
                    break;
                case 'cookie':
                    return Request::cookie('token');
                    break;
                case 'param':
                    return Request::get('token');
                    break;
                default:
                    return null;
            }
        }
    }
}
